﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Aleator.Core
{
    /// <summary>
    /// Rules for constructing keys and chords in the 12 tone chromatic system.
    /// </summary>
    public static class CircleOfFifths
    {
        #region Members

        private static Dictionary<ModeName, Step[]> m_modes = new Dictionary<ModeName, Step[]>();
        private static NoteFactory m_noteFactory = new ConcreteNoteFactory();
        private static ModeName[] m_activeModes;

        //Not needed yet
        //static private ChromaticScaleNote m_modalTonic;

        private static int[] m_sharpNotation = { 5, 6, 7, 8, 9, 10, 11, 17, 18, 19, 20, 21, 22, 23 };
        private static int[] m_flatNotation = { 0, 1, 2, 3, 4, 12, 13, 14, 15, 16 };

        #endregion

        #region Enums
#pragma warning disable 1591

        /// <summary>
        /// String representations of the 12 tones in the chromatic scale
        /// </summary>
        public enum ChromaticScaleNote
        {
            C,
            CSharp_DFlat,
            D,
            DSharp_EFlat,
            E,
            F,
            FSharp_GFlat,
            G,
            GSharp_AFlat,
            A,
            ASharp_BFlat,
            B
        }

        /// <summary>
        /// Step enumerator
        /// </summary>
        public enum Step
        {
            Half = 1,
            Whole,
            OneAndAHalf
        }

        /// <summary>
        /// Music mode enumerator
        /// </summary>
        public enum ModeName
        {
            Ionian = 1,
            IonianSharpFifth,
            Dorian,
            DorianFlatSecond,
            DorianSharpFourth,
            Phrygian,
            Lydian,
            LydianSharpSecond,
            Mixolydian,
            MixolydianFlatSixth,
            Aeolian,
            Locrian,
            MelodicMinor,
            LydianAugmented,
            LydianDominant,
            LocrianNaturalSecond,
            LocrianNaturalSixth,
            AlteredDominant,
            HarmonicMinor,
            PhrygianDominant,
            AlteredDominantDiminishedSeventh
        }

        /// <summary>
        /// Current key signature
        /// </summary>
        public enum Key
        {
            BFlatMinor = 0,
            FMinor = 1,
            CMinor = 2,
            GMinor = 3,
            DMinor = 4,
            AMinor = 5,
            EMinor = 6,
            BMinor = 7,
            FSharpMinor = 8,
            CSharpMinor = 9,
            GSharpMinor = 10,
            DSharpMinor = 11,
            DFlatMajor = 12,
            AFlatMajor = 13,
            EFlatMajor = 14,
            BFlatMajor = 15,
            FMajor = 16,
            CMajor = 17,
            GMajor = 18,
            DMajor = 19,
            AMajor = 20,
            EMajor = 21,
            BMajor = 22,
            FSharpMajor = 23,
            BFlatHarmonicMinor = 24,
            FHarmonicMinor = 25,
            CHarmonicMinor = 26,
            GHarmonicMinor = 27,
            DHarmonicMinor = 28,
            AHarmonicMinor = 29,
            EHarmonicMinor = 30,
            BHarmonicMinor = 31,
            FSharpHarmonicMinor = 32,
            CSharpHarmonicMinor = 33,
            GSharpHarmonicMinor = 34,
            DSharpHarmonicMinor = 35,
        }

        public static Dictionary<int, string> KeyBindingList;

        #pragma warning restore 1591
        #endregion

        #region Properties

        /// <summary>
        /// The key currently being used
        /// </summary>
        static public Key CurrentKey;

        /// <summary>
        /// Boolean major/minor value
        /// </summary>
        static public bool IsMajor = true;

        /// <summary>
        /// Name of the mode we are currently playing in
        /// </summary>
        static public ModeName CurrentModeName;

        /// <summary>
        /// Step values for the current mode
        /// </summary>
        static public List<Step> CurrentMode = new List<Step>();

        /// <summary>
        /// Notes "allowed" within the current key sig
        /// </summary>
        static public string[] Scale = new String[7];

        /// <summary>
        /// List of notes currently available
        /// </summary>
        static public List<Aleator.Core.ConcreteNote> Notes = new List<Aleator.Core.ConcreteNote>();

        /// <summary>
        /// List of currently available chords
        /// </summary>
        static public List<Chord> Chords;

        /// <summary>
        /// Tonic enum for this key
        /// </summary>
        static public ChromaticScaleNote Tonic;

        /// <summary>
        /// Tonic for current Key
        /// </summary>
        static public int TonicNoteID;

        #endregion

        #region Public Methods

        /// <summary>
        /// Set the current key based on on a string
        /// </summary>
        /// <param name="keyName">Name of the key to be used</param>
        public static void SetKey(string keyName)
        {
            foreach (Key key in Enum.GetValues(typeof(Key)))
            {
                if (keyName == key.ToString())
                {
                    CurrentKey = key;
                    break;
                }
            }

            string keyString = CurrentKey.ToString();
            string tonic = "";

            //Figure out if it's a major or minor key
            if (keyString.Substring(keyString.Length - 5, 5) == "Minor")
            {
                IsMajor = false;

                //Could be playing in either of the three "minor" modes
                //ModeName[] minorModeNumbers = new ModeName[3] { ModeName.Aeolian, ModeName.MelodicMinor, ModeName.HarmonicMinor };
                //CurrentModeName = minorModeNumbers[Session.rndGenerator.Next(1, 3)];

                //Just use natural for now
                CurrentModeName = ModeName.Aeolian;
            }
            else
                CurrentModeName = ModeName.Ionian;

            //First we need to determine the tonic
            if (keyString.Substring(1, 5) == "Major" || keyString.Substring(1, 5) == "Minor")
            {
                tonic = keyString.Substring(0, 1);
                Tonic = (ChromaticScaleNote)Enum.Parse(typeof(ChromaticScaleNote), tonic);
            }
            else if (keyString.Substring(1, 4) == "Flat")
            {
                tonic = keyString.Substring(0, 5);
                foreach (ChromaticScaleNote n in Enum.GetValues(typeof(ChromaticScaleNote)))
                {
                    if (n.ToString().IndexOf(tonic) > 0)
                    {
                        Tonic = n;
                        break;
                    }
                }
            }
            else
            {
                tonic = keyString.Substring(0, 6);

                foreach (ChromaticScaleNote n in Enum.GetValues(typeof(ChromaticScaleNote)))
                {
                    if (n.ToString().IndexOf("_") > 0)
                    {
                        string[] notes = n.ToString().Split('_');

                        if (notes[0] == tonic || notes[1] == tonic)
                        {
                            Tonic = n;
                            break;
                        }
                    }
                    else if (n.ToString() == tonic)
                    {
                        Tonic = n;
                        break;
                    }
                }
            }

            BuildModes();
            BuildScale();
            SetNotes();
            SetChords();
        }

        //Not needed yet
        //public static void SetMode(string modename)
        //{
        //    CurrentMode.Clear();
        //    CurrentModeName = (ModeName)System.Enum.Parse(typeof(ModeName), modename);
        //    CurrentMode.AddRange(m_modes.Where(m => m.Key == CurrentModeName).First().Value.ToList());
        //}

        #endregion

        #region Private Methods

        private static string GetEnumDescription(Enum value)
        {
            string note = value.ToString();

            if (note.Contains('_'))
            {
                if (m_flatNotation.Contains((int)CurrentKey))
                    return note.Substring(note.IndexOf('_') + 1);
                else
                    return note.Substring(0, note.IndexOf('_'));
            }

            return value.ToString();
        }

        private static void BuildModes()
        {
            if (m_modes.Count == 0)
                PopulateModes();

            CurrentMode.Clear();
            CurrentMode.AddRange(m_modes.Where(m => m.Key == CurrentModeName).First().Value.ToList());

            if (m_activeModes == null)
                m_activeModes = new ModeName[7];

            switch (CurrentModeName)
            {
                case ModeName.Ionian:
                    m_activeModes[0] = ModeName.Ionian;
                    m_activeModes[1] = ModeName.Dorian;
                    m_activeModes[2] = ModeName.Phrygian;
                    m_activeModes[3] = ModeName.Lydian;
                    m_activeModes[4] = ModeName.Mixolydian;
                    m_activeModes[5] = ModeName.Aeolian;
                    m_activeModes[6] = ModeName.Locrian;
                    break;
                case ModeName.Aeolian:
                    m_activeModes[0] = ModeName.Aeolian;
                    m_activeModes[1] = ModeName.Locrian;
                    m_activeModes[2] = ModeName.Ionian;
                    m_activeModes[3] = ModeName.Dorian;
                    m_activeModes[4] = ModeName.Phrygian;
                    m_activeModes[5] = ModeName.Lydian;
                    m_activeModes[6] = ModeName.Mixolydian;
                    break;
                case ModeName.MelodicMinor:
                    m_activeModes[0] = ModeName.MelodicMinor;
                    m_activeModes[1] = ModeName.DorianFlatSecond;
                    m_activeModes[2] = ModeName.LydianAugmented;
                    m_activeModes[3] = ModeName.LydianDominant;
                    m_activeModes[4] = ModeName.MixolydianFlatSixth;
                    m_activeModes[5] = ModeName.LocrianNaturalSecond;
                    m_activeModes[6] = ModeName.AlteredDominant;
                    break;
                case ModeName.HarmonicMinor:
                    m_activeModes[0] = ModeName.HarmonicMinor;
                    m_activeModes[1] = ModeName.LocrianNaturalSixth;
                    m_activeModes[2] = ModeName.IonianSharpFifth;
                    m_activeModes[3] = ModeName.DorianSharpFourth;
                    m_activeModes[4] = ModeName.PhrygianDominant;
                    m_activeModes[5] = ModeName.LydianSharpSecond;
                    m_activeModes[6] = ModeName.AlteredDominantDiminishedSeventh;
                    break;
            }
        }

        private static void SetChords()
        {
            if (Chords == null)
                Chords = new List<Chord>();
            else
                Chords.Clear();

            for (int interval = 1; interval < 8; interval++)
            {
                Chords.Add(new Chord(Notes[interval - 1], interval, false));
            }
        }

        private static void BuildScale()
        {
            int newNoteNum = 0;

            Scale[0] = GetEnumDescription((ChromaticScaleNote)Enum.ToObject(typeof(ChromaticScaleNote), (int)Tonic));

            //Only need six more intervals (the 8th is a repeat of the tonic)
            for (int i = 1; i <= 6; i++)
            {
                Step step = CurrentMode[i - 1];

                if (i == 1)
                    newNoteNum = (int)Tonic + (int)step;
                else
                    newNoteNum = newNoteNum + (int)step;

                if (newNoteNum == 12) newNoteNum = 0;
                if (newNoteNum == 13) newNoteNum = 1;

                Scale[i] = GetEnumDescription((ChromaticScaleNote)Enum.ToObject(typeof(ChromaticScaleNote), newNoteNum));
            }
        }

        private static void SetNotes()
        {
            //Initialize note list
            if (Notes == null)
                Notes = new List<ConcreteNote>();
            else
                Notes.Clear();

            //Figure out the lowest MIDIoctave in use
            int lowOctave = Session.HomeOctave.Value - Session.OctaveVariance;
            int highOctave = Session.HomeOctave.Value + Session.OctaveVariance;

            //-5 is the lowest available
            if (lowOctave < -5)
                lowOctave = -5;

            //5 is the highest available
            if (highOctave > 5)
                highOctave = 5;

            TonicNoteID = ((Session.HomeOctave.Value + 5) * 12) + (int)Tonic;
            //TonicNote = new Note(Tonic, tonicNoteID, 1, Session.HomeOctave);

            //TODO: Remember why we're adding 5 here. Fix the fact that notes for the top octave are not added.
            //Calculate lowest note. MIDI keyboard is zero based so add 5 to the lowest octave
            //Multiply by 12 to find the lowest C then add Tonic (also zero based)
            int lowNote = ((lowOctave + 5) * 12) + (int)Tonic;

            //Same principle for highest note
            int highNote = ((highOctave + 5) * 12) + (int)Tonic + 6;

            //Upper octave is abbreviated
            if (highNote > Session.HighNote)
                highNote = Session.HighNote;

            //Vars to use in the loop
            int note = lowNote;
            int octave = lowOctave;
            int i = 0;

            while (note <= highNote)
            {
                //Add this note to the list
                if (note < 12)
                    Notes.Add((ConcreteNote)m_noteFactory.GetNote((ChromaticScaleNote)note, GetEnumDescription((ChromaticScaleNote)note), note, i, octave));
                else
                    Notes.Add((ConcreteNote)m_noteFactory.GetNote((ChromaticScaleNote)(note % 12), GetEnumDescription((ChromaticScaleNote)(note % 12)), note, i, octave));

                //To move up the scale use the step value at the current interval
                note = note + (int)CurrentMode[i];

                //Increment interval and octave (if needed)
                if (i == 6)
                {
                    i = 0;
                    octave++;
                }
                else
                    i++;
            }
        }

        public static void AddAccidental(int interval, int adjustment)
        {
            int highOctave = Session.HomeOctave.Value + Session.OctaveVariance;

            if (highOctave > 5)
                highOctave = 5;

            int highNote = ((highOctave + 5) * 12) + (int)Tonic + 6;

            if (highNote > Session.HighNote)
                highNote = Session.HighNote;

            int note;

            //Get all of the notes for interval to which we are applying accidentals
            List<ConcreteNote> basenotes = Notes.Where(n => n.Interval == interval).OrderBy(n => n.ChromaticNote).ToList();

            foreach (ConcreteNote cn in basenotes)
            {
                note = (int)cn.ChromaticNote + adjustment;

                //Add this note to the list
                if (note < 12)
                    Notes.Add((ConcreteNote)m_noteFactory.GetNote((ChromaticScaleNote)note, GetEnumDescription((ChromaticScaleNote)note), note, cn.Interval, cn.Octave, adjustment));
                else if (note < highNote)
                    Notes.Add((ConcreteNote)m_noteFactory.GetNote((ChromaticScaleNote)(note % 12), GetEnumDescription((ChromaticScaleNote)(note % 12)), note, cn.Interval, cn.Octave, adjustment));
            }
        }

        private static void PopulateModes()
        {
            m_modes.Add(ModeName.Ionian, new Step[7] { Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Half });
            m_modes.Add(ModeName.IonianSharpFifth, new Step[7] { Step.Whole, Step.Whole, Step.Half, Step.OneAndAHalf, Step.Half, Step.Whole, Step.Half });
            m_modes.Add(ModeName.Dorian, new Step[7] { Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole });
            m_modes.Add(ModeName.DorianSharpFourth, new Step[7] { Step.Whole, Step.Half, Step.OneAndAHalf, Step.Half, Step.Whole, Step.Half, Step.Whole });
            m_modes.Add(ModeName.Phrygian, new Step[7] { Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.PhrygianDominant, new Step[7] { Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.Lydian, new Step[7] { Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half });
            m_modes.Add(ModeName.LydianAugmented, new Step[7] { Step.Whole, Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Half });
            m_modes.Add(ModeName.LydianDominant, new Step[7] { Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Half, Step.Whole });
            m_modes.Add(ModeName.LydianSharpSecond, new Step[7] { Step.Whole, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half });
            m_modes.Add(ModeName.Mixolydian, new Step[7] { Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half, Step.Whole });
            m_modes.Add(ModeName.MixolydianFlatSixth, new Step[7] { Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Half, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.Aeolian, new Step[7] { Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.Locrian, new Step[7] { Step.Half, Step.Whole, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.LocrianNaturalSecond, new Step[7] { Step.Whole, Step.Half, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.LocrianNaturalSixth, new Step[7] { Step.Half, Step.Whole, Step.Whole, Step.Half, Step.OneAndAHalf, Step.Half, Step.Whole });
            m_modes.Add(ModeName.MelodicMinor, new Step[7] { Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.AlteredDominant, new Step[7] { Step.Half, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Whole, Step.Whole });
            m_modes.Add(ModeName.AlteredDominantDiminishedSeventh, new Step[7] { Step.Half, Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half, Step.OneAndAHalf });
            m_modes.Add(ModeName.HarmonicMinor, new Step[7] { Step.Whole, Step.Half, Step.Whole, Step.Whole, Step.Half, Step.OneAndAHalf, Step.Half });
        }

        #endregion
    }
}
